import cx from 'classnames'
import React, { CSSProperties } from 'react'
import { BehaviorSubject, noop, Subscription } from 'rxjs'
import { RTColumn } from '../interfaces'
import { calculateRenderInfo } from './Calculations'
import TableHeader from './TableHeader'
import { getFullRenderRange } from '../helpers/rangeManager'
import { TableDOMHelper } from '../helpers/tableDomHelper'
import { TableBody } from './TableBody'
import { RenderInfo, VerticalRenderRange } from '../interfaces'
import { BaseTableCSSVariables, Classes, StyledArtTableWrapper } from '../styles'
import {
  getScrollbarSize,
  STYLED_REF_PROP,
  sum,
  syncScrollLeft,
  throttledWindowResize,
} from '../helpers/utils'


export type PrimaryKey = string | ((row: any) => string)

export interface BaseTableProps {
  /** 主键 */
  primaryKey?: PrimaryKey
  /** 表格展示的数据源 */
  dataSource: any[]
  /** 表格的列配置 */
  columns: RTColumn[]
  /** 表格头部是否置顶，默认为 true */
  isStickyHeader?: boolean
  /** 表格置顶后，距离顶部的距离 */
  stickyTop?: number
  /** 自定义类名 */
  className?: string
  /** 自定义内联样式 */
  style?: CSSProperties & BaseTableCSSVariables
  /** 表格是否具有头部 */
  hasHeader?: boolean
  /** 使用来自外层 div 的边框代替单元格的外边框 */
  useOuterBorder?: boolean

  /** 覆盖表格内部用到的组件 */
  components?: {
    /** 覆盖内部渲染 tbody>tr 元素的组件，一般用于在 tr 上设置 context */
    Row?: React.ComponentType<{ row: any; rowIndex: number; trProps: unknown }>
    /** 覆盖内部渲染 tbody>td 元素的组件，一般用于在 td 上设置 context */
    Cell?: React.ComponentType<{ row: any; rowIndex: number; colIndex: number; tdProps: unknown; column: RTColumn }>
    /** 覆盖内部渲染 tbody 元素的组件 */
    TableBody?: React.ComponentType<{ tbodyProps: unknown }>
  }

  /** 列的默认宽度 */
  defaultColumnWidth?: number

  getRowProps?(row: any, rowIndex: number): React.HTMLAttributes<HTMLTableRowElement>
}

interface BaseTableState {
  /** 是否要展示自定义滚动条(stickyScroll) */
  hasScroll: boolean

  /** 
   * 只有当「整个表格的宽度」小于「每一列渲染宽度之和」时，lock sections 才需要被渲染 */
  needRenderLock: boolean

  /** 纵向虚拟滚动偏移量 */
  offsetY: number
  /** 纵向虚拟滚动 最大渲染尺寸 */
  maxRenderHeight: number
  /** 横向虚拟滚动偏移量 */
  offsetX: number
  /** 横向虚拟滚动 最大渲染尺寸 */
  maxRenderWidth: number
}

export class BaseTable extends React.Component<BaseTableProps, BaseTableState> {
  static defaultProps = {
    hasHeader: true,
    isStickyHeader: true,
    stickyTop: 0,
    stickyScrollHeight: 'auto',
    components: {},
    getRowProps: noop,
    dataSource: [] as any[],
  }


  private tableWrapperRef = React.createRef<HTMLDivElement>()
  private domHelper: TableDOMHelper
  private mainSubscription = new Subscription()

  // 最近一次渲染的计算结果缓存
  private lastRenderInfo: RenderInfo

  private props$: BehaviorSubject<BaseTableProps>

  constructor(props: Readonly<BaseTableProps>) {
    super(props)

    this.state = {
      hasScroll: true,
      needRenderLock: true,
      offsetY: 0,
      offsetX: 0,
      maxRenderHeight: 600,
      maxRenderWidth: 800,
    }
  }

  /** 更新滚动条滑块宽度 */
  private updateStickyScroll() {
    const { stickyScroll, table, stickyScrollItem } = this.domHelper

    if (!table) {
      return
    }
    const tableBodyHtmlTable = this.domHelper.getTableBodyHtmlTable()
    const innerTableWidth = tableBodyHtmlTable.offsetWidth
    const tableWidth = table.offsetWidth

    const stickyScrollHeight = getScrollbarSize().height

    stickyScroll.style.marginTop = `-${stickyScrollHeight + 1}px`

    if (tableWidth >= innerTableWidth) {
      if (this.state.hasScroll) {
        this.setState({ hasScroll: false })
      }
    } else {
      if (!this.state.hasScroll && stickyScrollHeight > 5) {
        // 兼容
        this.setState({ hasScroll: true })
      }
    }
    // 设置子节点宽度
    stickyScrollItem.style.width = `${innerTableWidth}px`
  }

  private renderTableHeader(info: RenderInfo) {
    const { stickyTop, hasHeader } = this.props

    return (
      <div
        className={cx(Classes.tableHeader, 'no-scrollbar')}
        style={{
          top: stickyTop === 0 ? undefined : stickyTop,
          display: hasHeader ? undefined : 'none',
        }}
      >
        <TableHeader info={info} />
      </div>
    )
  }

  getVerticalRenderRange(): VerticalRenderRange {
    const { dataSource } = this.props
    const rowCount = dataSource.length
    return getFullRenderRange(rowCount)
  }

  private renderTableBody(info: RenderInfo) {
    const { dataSource, getRowProps, primaryKey, components } = this.props
    const tableBodyClassName = cx(Classes.tableBody, Classes.horizontalScrollContainer)

    const { topIndex, bottomBlank, topBlank, bottomIndex } = info.verticalRenderRange

    return (
      <div className={tableBodyClassName}>
        {topBlank > 0 && (
          <div key="top-blank" className={cx(Classes.virtualBlank, 'top')} style={{ height: topBlank }} />
        )}
        <TableBody
          components={components}
          tbodyHtmlTag="tbody"
          getRowProps={getRowProps}
          primaryKey={primaryKey}
          data={dataSource.slice(topIndex, bottomIndex)}
          horizontalRenderInfo={info}
          verticalRenderInfo={{
            first: 0,
            offset: topIndex,
            limit: bottomIndex,
            last: dataSource.length - 1,
          }}
        />
        {bottomBlank > 0 && (
          <div key="bottom-blank" className={cx(Classes.virtualBlank, 'bottom')} style={{ height: bottomBlank }} />
        )}
      </div>
    )
  }


  private renderStickyScroll() {
    const { hasScroll } = this.state
    return (
      <div
        className={cx(Classes.stickyScroll, Classes.horizontalScrollContainer)}
        style={{
          display: hasScroll ? 'block' : 'none',
        }}
      >
        <div className={Classes.stickyScrollItem} />
      </div>
    )
  }

  render() {
    const info = calculateRenderInfo(this)
    this.lastRenderInfo = info

    const {
      dataSource,
      className,
      style,
      hasHeader,
      useOuterBorder,
      isStickyHeader,
    } = this.props


    const tableWrapperClassName = cx(
      Classes.tableWrapper,
      {
        'use-outer-border': useOuterBorder,
        empty: dataSource.length === 0,
        lock: info.hasLockColumn,
        'has-header': hasHeader,
        'sticky-header': isStickyHeader,
      },
      className,
    )

    const tableWrapperProps = {
      className: tableWrapperClassName,
      style,
      [STYLED_REF_PROP]: this.tableWrapperRef,
    }

    return (
      <StyledArtTableWrapper {...tableWrapperProps}>
       
          <div className={Classes.table}>
            {this.renderTableHeader(info)}
            {this.renderTableBody(info)}
          </div>
          {this.renderStickyScroll(info)}
       
      </StyledArtTableWrapper>
    )
  }

  componentDidMount() {
    this.updateDOMHelper()
    this.props$ = new BehaviorSubject(this.props)
    this.initSubscriptions()
    this.didMountOrUpdate()
  }

  componentDidUpdate(prevProps: Readonly<BaseTableProps>, prevState: Readonly<BaseTableState>) {
    this.updateDOMHelper()
    this.props$.next(this.props)
    this.didMountOrUpdate(prevProps, prevState)
  }

  private didMountOrUpdate(prevProps?: Readonly<BaseTableProps>, prevState?: Readonly<BaseTableState>) {
    this.updateScrollLeftWhenLayoutChanged(prevProps, prevState)
  }

  private updateScrollLeftWhenLayoutChanged(
    prevProps?: Readonly<BaseTableProps>,
    prevState?: Readonly<BaseTableState>,
  ) {
    if (prevState != null) {
      if (!prevState.hasScroll && this.state.hasScroll) {
        this.domHelper.stickyScroll.scrollLeft = 0
      }
    }

  }

  private initSubscriptions() {
    const { tableHeader, tableBody, stickyScroll } = this.domHelper

    this.mainSubscription.add(
      throttledWindowResize.subscribe(() => {
        this.updateStickyScroll()
        this.handleNeedRenderLock()
      }),
    )
    // Header Body Scroll 滚动同步
    this.mainSubscription.add(
      syncScrollLeft([tableHeader, tableBody, stickyScroll], () => {
        // 滚动同步回调
      }),
    )

  }

  componentWillUnmount() {
    this.mainSubscription.unsubscribe()
  }

  /** 更新 DOM 节点 */
  private updateDOMHelper() {
    this.domHelper = new TableDOMHelper(this.tableWrapperRef.current)
  }

  /** 计算表格所有列的宽度和，判断表格是否需要锁列 */
  private handleNeedRenderLock() {
    const { needRenderLock } = this.state
    const { flat, hasLockColumn } = this.lastRenderInfo

    if (hasLockColumn) {
      const sumOfColWidth = sum(flat.full.map((col) => col.width))
      const nextNeedRenderLock = sumOfColWidth > this.domHelper.table.clientWidth
      if (needRenderLock !== nextNeedRenderLock) {
        this.setState({ needRenderLock: nextNeedRenderLock })
      }
    } else {
      if (needRenderLock) {
        this.setState({ needRenderLock: false })
      }
    }
  }
}
